﻿using System;
using System.Reflection;
using NLog;
using Skyline;
using SmartQuant;

namespace QuantBox
{
    // ReSharper disable once InconsistentNaming
    public class QBTimeBarFactoryItem : TimeBarFactoryItem
    {
        private const int OffsetSeconds = 1;

        private DateTime _closeDateTime;
        private ClockType _clockType;
        private readonly bool _enableLog;
        private readonly bool _disableBarSlice;
        private readonly Logger _logger;
        private TradingTimeRange _timeRanges;
        private Framework? _framework;
        private bool _delayedBarOpen;
        private double _turnover;
        private double _openInt;
        private DateTime _time;

        public override BarFactoryItem Clone()
        {
            return new QBTimeBarFactoryItem(instrument, barSize, barInput, _enableLog);
        }

        private Framework? GetFramework()
        {
            var fields = factory.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
            foreach (var field in fields)
            {
                if (field.FieldType == typeof(Framework))
                {
                    return (Framework)field.GetValue(factory);
                }
            }
            return null;
        }

        public QBTimeBarFactoryItem(Instrument instrument, long barSize, BarInput input = BarInput.Trade,bool enableLog = false, string? instanceName = null, bool disableBarSlice = false)
            : base(instrument, barSize, input, ClockType.Exchange)
        {
            _enableLog = enableLog;
            _disableBarSlice = disableBarSlice;
            _logger = _enableLog ? LogManager.GetLogger($"tbf.{instanceName ?? "main"}.{instrument.Symbol}.{barSize}") : LogManager.CreateNullLogger();

            _clockType = ClockType.Exchange;
            LoadTradingTimeRange();
        }

        public bool IncludeFirstTick { get; set; } = true;

        public ClockType ClockType
        {
            get => _clockType;
            set => _clockType = value;
        }

        private void GetOpenCloseDateTime(DataObject tick, out DateTime openDateTime, out DateTime closeDateTime)
        {
            openDateTime = GetBarOpenDateTime(tick);
            closeDateTime = barSize == QuantBoxConst.MonthBarSize
                ? openDateTime.Date.AddMonths(1)
                : openDateTime.AddSeconds(barSize);

            closeDateTime = FixCloseDateTime(closeDateTime);
            if (_framework!.Mode == FrameworkMode.Realtime)
            {
            }
        }

        protected override void OnReminder(DateTime datetime)
        {
            RaiseBar(null);
        }

        private DateTime GetBarOpenDateTime(DataObject obj)
        {
            var tick = (Tick)obj;
            if (IncludeFirstTick)
            {
                var timeChanged = false;
                if (_timeRanges.InFristMd(_time.TimeOfDay))
                {
                    _time = _time.Date.Add(
                        _time.TimeOfDay > _timeRanges.CloseTime
                            ? _timeRanges.NightOpenTime
                            : _timeRanges.OpenTime);
                    timeChanged = true;
                }
                if (timeChanged)
                {
                    if (_framework!.Mode == FrameworkMode.Realtime)
                    {
                        _delayedBarOpen = true;
                    }

                    if (_clockType == ClockType.Exchange)
                    {
                        tick.ExchangeDateTime = _time;
                    }
                    else
                    {
                        tick.DateTime = _time;
                    }
                }
            }
            return base.GetBarOpenDateTime(tick, _clockType);
        }

        private DateTime FixCloseDateTime(DateTime dateTime, bool backwards = true)
        {
            var time = dateTime.TimeOfDay;
            if (_timeRanges!.IsEndPoint(time.Hours, time.Minutes))
            {
                return dateTime.AddSeconds(backwards ? OffsetSeconds : -OffsetSeconds);
            }
            return dateTime;
        }

        private void RaiseBar(Tick? tick)
        {
            if (bar == null)
            {
                return;
            }

            bar.DateTime = FixCloseDateTime(_closeDateTime, false);
            bar.SetTurnover(_turnover);
            bar.OpenInt = (long)_openInt;
            if (_framework!.Mode == FrameworkMode.Realtime)
            {
                LogTrade(tick);
                _logger.Debug($"close_bar,{bar.OpenDateTime:dd_HH:mm:ss},{bar.CloseDateTime:dd_HH:mm:ss}");
            }

            if (_disableBarSlice)
            {
                _framework.EventManager.BarSliceFactory.Clear();
            }
            EmitBar();
        }

        private void CreateBar(Tick tick)
        {
            GetOpenCloseDateTime(tick, out var openDateTime, out _closeDateTime);
            if (_framework!.Mode == FrameworkMode.Realtime)
            {
                LogTrade(tick);
                _logger.Debug($"new_bar,{openDateTime:dd_HH:mm:ss},{_closeDateTime:dd_HH:mm:ss}");
            }
#if Compatibility
            bar = OpenQuant.Helper.NewBar(
                time,
                tick.DateTime,
                ProviderId,
                tick.InstrumentId,
                barType,
                barSize,
                tick.Price, tick.Price, tick.Price, tick.Price, OpenQuant.Helper.GetDoubleSize(tick));
#else
            bar = new Bar(openDateTime,
                tick.DateTime,
                ProviderId,
                tick.InstrumentId,
                barType,
                barSize,
                tick.Price, tick.Price, tick.Price, tick.Price, tick.Size);
#endif
            _turnover = 0;
            if (!_delayedBarOpen)
            {
                EmitBarOpen();
            }

            if (_framework!.Mode == FrameworkMode.Simulation)
            {
                AddReminder(_closeDateTime, _clockType);
            }
        }

        private void LoadTradingTimeRange()
        {
            _timeRanges = TradingCalendar.Instance.GetTimeRange(instrument, DateTime.Today)!;
        }

        private void LogTrade(Tick? trade)
        {
            if (trade == null)
            {
                return;
            }
#if Compatibility
            _logger.Debug($"{trade.Price},{OpenQuant.Helper.GetIntSize(trade)},{trade.DateTime: HH:mm:ss},{trade.ExchangeDateTime: HH:mm:ss}");
#else
            _logger.Debug($"{trade.Price},{trade.Size},{trade.DateTime:HH:mm:ss},{trade.ExchangeDateTime:HH:mm:ss}");
#endif
        }

        protected override void OnData(DataObject obj)
        {
            _framework ??= GetFramework();
            var trade = (Trade)obj;
            _time = _clockType == ClockType.Exchange ? trade.ExchangeDateTime : trade.DateTime;

            if (bar != null)
            {
                if (_delayedBarOpen && bar.OpenDateTime <= _time)
                {
                    _delayedBarOpen = false;
                    EmitBarOpen();
                }

                if (_closeDateTime < _time)
                {
                    RaiseBar(trade);
#if Compatibility
                    if (OpenQuant.Helper.GetIntSize(trade) == 0)
#else
                    if (trade.Size == 0)
#endif
                    {
                        return;
                    }

                    CreateBar(trade);
                }
                base.OnData(trade);
                _turnover += trade.GetTurnover();
                _openInt = trade.GetOpenInterest();

                if (_framework!.Mode == FrameworkMode.Simulation)
                {
                    return;
                }

                if (_closeDateTime == trade.ExchangeDateTime)
                {
                    RaiseBar(trade);
                }
                else
                {
                    if (trade.GetClosePrice() > 0)
                    {
                        RaiseBar(trade);
                    }
                }
            }
            else
            {
#if Compatibility
                if (OpenQuant.Helper.GetIntSize(trade) == 0) 
#else
                if (trade.Size == 0)
#endif
                {
                    return;
                }

                CreateBar(trade);
            }
        }
    }
}